%%%-------------------------------------------------------------------
%%% @author Martin J. Logan <martin@erlware.com>
%%% @copyright 2007 Erlware
%%% Created: Tue Jan 23 15:32:18 CST 2007
%%%-------------------------------------------------------------------
-module(fax_util).

%%--------------------------------------------------------------------
%% Include files
%%--------------------------------------------------------------------
-include("eunit.hrl").
-include("faxien.hrl").


%%--------------------------------------------------------------------
%% External exports
%%--------------------------------------------------------------------
-export([
	 foreach_erts_vsn/2,
	 print_logfile_info/0,
	 find_highest_vsn/3,
	 find_highest_vsn/4,
	 find_highest_vsn/5,
	 create_tmp_package_dir/1,
	 create_tmp_package_dir/0,
	 delete_potential_package_tmp_dir/1,
	 stage_package_tarball/1,
	 commandline_apply/1,
	 to_app_dirs/1,
	 if_atom_to_string/1,
	 flatten_term/1,
	 repo_list/1,
	 find/2,
         do_until/3, 
         do_until/2, 
	 add_pzs/1,
	 consult_rel_file/1,
	 set_executable_perms/1,
	 set_all_executable_perms/1
        ]).

%%====================================================================
%% External functions
%%====================================================================


%%-------------------------------------------------------------------
%% @depricated
%% @doc
%% Return the latest version and repo that contains it for a given package and repos.
%%
%% @spec find_highest_vsn(Repos, PackageName, Side) -> {ok, {Repo, Vsn}} | {error, Reason}
%%  where 
%%   Side = lib | releases
%% @end
%%-------------------------------------------------------------------
find_highest_vsn(Repos, PackageName, Side) ->
    case catch find_highest_vsn2(Repos, PackageName, Side) of
	{ok, {_Repo, _HighVsn}} = Reply -> Reply;
	Exp                             -> {error, {"Package not found: " ++ PackageName,Exp}}
    end.
				 
find_highest_vsn2(Repos, PackageName, Side) ->
    VsnLists = lists:flatten(lists:foldl(fun(Repo, Acc) -> 
				   SysInfo  = ewr_util:system_info(),
				   Suffixes = ewr_util:gen_repo_stub_suffix(PackageName, [SysInfo, "Generic"], Side),
				   lists:foldl(fun(Suf, Acc2) -> 
						       ?INFO_MSG("Checking for highest version of ~p in ~s/~s~n", 
									     [PackageName, Repo, Suf]),
						       case repo_list(Repo ++ "/" ++ Suf) of
							   {ok, Vsns} -> 
							       [[{Repo, Vsn} || Vsn <- lists:reverse(Vsns)]|Acc2]; 
							   {error, _Reason} -> 
							       Acc2
						       end
					       end, Acc, Suffixes)
			   end, [], Repos)),
    ?INFO_MSG("find_highest_vsn list of remote versions ~p~n", [VsnLists]),
    {Repo, HighVsn} = hd(lists:sort(fun({_, V1}, {_, V2}) -> ewr_util:is_version_greater(V1, V2) end, VsnLists)),
    ?INFO_MSG("find_highest_vsn list of remote versions ~p and highest vsn is ~p~n", [VsnLists, {Repo, HighVsn}]),
    {ok, {Repo, HighVsn}}.

%%-------------------------------------------------------------------
%% @doc
%% Return the latest version lower than the VsnThreshold and repo that contains it for a given package and repos.
%%
%% <pre>
%% Variables:
%%  VsnThreshold - is used to indicate that the highest version possible is to be found but no higher than VsnThreshold.
%% </pre>
%%
%% @spec find_highest_vsn(Repos, TargetErtsVsn, PackageName, Side, VsnThreshold) -> {ok, {Repo, Vsn}} | {error, Reason}
%%  where 
%%   Side = lib | releases
%%   VsnThreshold = string() | infinity
%% @end
%%-------------------------------------------------------------------
find_highest_vsn(Repos, TargetErtsVsn, PackageName, Side, VsnThreshold) ->
    case catch find_highest_vsn2(Repos, TargetErtsVsn,  PackageName, Side, VsnThreshold) of
	{ok, {_Repo, _HighVsn}} = Reply -> Reply;
	Exp                             -> {error, {"Package not found: " ++ PackageName,Exp}}
    end.
				 
find_highest_vsn2(Repos, TargetErtsVsn, PackageName, Side, VsnThreshold) ->
    VsnLists = lists:flatten(lists:foldl(fun(Repo, Acc) -> 
						 SysInfo  = ewr_util:system_info(),
						 Suffixes = ewr_util:gen_multi_erts_repo_stub_suffix(TargetErtsVsn, 
												     PackageName,
												     [SysInfo, "Generic"], 
												     Side),
				   lists:foldl(fun(Suf, Acc2) -> 
						       ?INFO_MSG("Checking for highest version of ~p in ~s/~s~n", 
									     [PackageName, Repo, Suf]),
						       case repo_list(Repo ++ "/" ++ Suf) of
							   {ok, Vsns} -> 
							       ?INFO_MSG("found vsns ~p~n", [Vsns]),
							       [[{Repo, Vsn} || Vsn <- lists:reverse(Vsns)]|Acc2]; 
							   {error, _Reason} -> 
							       Acc2
						       end
					       end, Acc, Suffixes)
			   end, [], Repos)),
    ?INFO_MSG("find_highest_vsn list of remote versions ~p~n", [VsnLists]),
    {Repo, HighVsn} =
	case VsnThreshold of
	    infinity ->
		hd(lists:sort(fun({_, V1}, {_, V2}) -> ewr_util:is_version_greater(V1, V2) end, VsnLists));
	    VsnThreshold ->
		lop_off_top(VsnLists, VsnThreshold)
	end,
    ?INFO_MSG("find_highest_vsn list of remote versions ~p and highest vsn is ~p~n", [VsnLists, {Repo, HighVsn}]),
    {ok, {Repo, HighVsn}}.


%% @equiv find_highest_vsn(Repos, TargetErtsVsn, PackageName, Side, infinity)
%% @spec find_highest_vsn(Repos, TargetErtsVsn, PackageName, Side) -> {ok, {Repo, Vsn}} | {error, Reason}
find_highest_vsn(Repos, TargetErtsVsn, PackageName, Side) ->
    find_highest_vsn(Repos, TargetErtsVsn, PackageName, Side, infinity).

lop_off_top(VsnLists, VsnThreshold) ->
    SortedVsnLists = lists:sort(fun({_, V1}, {_, V2}) -> ewr_util:is_version_greater(V1, V2) end, VsnLists),
    lop_off_top2(SortedVsnLists, VsnThreshold).

lop_off_top2([{Repo, Vsn}|T], VsnThreshold) ->
    case ewr_util:is_version_greater(VsnThreshold, Vsn) of
	true  -> {Repo, Vsn};
	false -> lop_off_top2(T, VsnThreshold) 
    end;
lop_off_top2([], VsnThreshold) ->
    {error, {"no packages with version less than ", VsnThreshold}}.
	    

%%--------------------------------------------------------------------
%% @doc take a package tarball, untar it into a temp directory, and return the path.
%% @spec stage_package_tarball(PackagePath) -> NewPackagePath | exit()
%% @end
%%--------------------------------------------------------------------
stage_package_tarball(PackagePath) ->
    {ok, TmpDirPath} = create_tmp_package_dir(),
    TmpTarFilePath   = lists:flatten([TmpDirPath, filename:basename(PackagePath)]),
    ?INFO_MSG("moving tar file into tmp dir ~p is ~p~n", [TmpDirPath, file:copy(PackagePath, TmpTarFilePath)]),
    {ok, CWD} = file:get_cwd(),
    ok = file:set_cwd(filename:dirname(TmpDirPath)),
    ok = erl_tar:extract(filename:basename(TmpTarFilePath), [compressed, verbose]),
    ok = file:set_cwd(CWD),
    [TmpPackagePath|_] = fax_util:find(TmpDirPath, ".*([a-zA-Z_]+-[0-9\.]+|erts).?$"),
    TmpPackagePath.

%%--------------------------------------------------------------------
%% @doc create a unique temorory directory for staging packages.
%% @spec create_tmp_package_dir(Prefix::string()) -> {ok, TmpDirPath} | {error, Reason}
%% @end
%%--------------------------------------------------------------------
create_tmp_package_dir(Prefix) ->
    TmpDirPath = lists:flatten([Prefix, "/.ewl_tmp-", integer_to_list(element(3, now())), "/"]) ,
    case ewl_file:mkdir_p(TmpDirPath) of
	ok    -> {ok, TmpDirPath};
	Error -> Error
    end.
	     
%% @spec create_tmp_package_dir() -> {ok, TmpDirPath} | {error, Reason}
%% @equiv create_tmp_package_dir(TmpDir)
create_tmp_package_dir() ->
    {ok, TmpDir} = fax_paths:get_installation_path(),
    create_tmp_package_dir(TmpDir ++ "/tmp").

%%--------------------------------------------------------------------
%% @doc delete a directory if it was created by stage_package_tarball/1
%% @spec delete_potential_package_tmp_dir(PotentialTmpDir) -> ok | exit()
%% @end
%%--------------------------------------------------------------------
delete_potential_package_tmp_dir(PotentialTmpDir) ->
    case regexp:match(PotentialTmpDir, ".ewl_tmp") of
	{match, _, _} -> ewl_file:delete_dir(filename:dirname(PotentialTmpDir));
	_             -> ok 
    end.


%%--------------------------------------------------------------------
%% @doc Allows any function to be called from the command line via erl -s
%% all arguments should be passed in the following manner:
%% erl -s mod func "arg1. " "arg2. "...  Be sure that proper escaping
%% of special characters has been done. Particularly, strings and spaces, 
%% if what you are sending as an arg is an erlang string make sure you escape
%% the quote " characters or they will be intepreted by the shell and
%% not passed to this function. \"hello\" makes it to this function
%% as '"hello"' which will be interpreted as the string "hello" whereas
%% "hello" makes it to this function as 'hello'.  All args to -s
%% are supplied to the function specified as atoms. 
%% <pre>
%%
%% Example Command Line Invocation:
%%  erl -s fax_util commandline_apply file list_dir \"/home/jdoe\" -s init stop  
%%
%% Variables:
%%  MFSList - a list containing the mod func and a list of args to apply comming via erl -s
%%
%% Types:
%%  MFAList = [Mod, Func|Args] Example [file, list_dir, "/var/log"]
%% </pre>
%% @spec commandline_apply(MFAList) -> void()
%% @end
%%--------------------------------------------------------------------
commandline_apply([_Mod]) ->
    faxien:help();
commandline_apply([Mod, Func|Args]) ->
    ?INFO_MSG("mod ~p:~p with raw args from commandline: ~w~n", [Mod, Func, Args]),
    TokedArgs   = lists:map(fun(ArgAtom) -> convert_string_to_terms(atom_to_list(ArgAtom)) end, no_space(Args)),
    ?INFO_MSG("apply the following: apply(~w, ~w, ~p)~n", [Mod, Func, TokedArgs]),
    try 
	case apply(Mod, Func, TokedArgs) of 
	    {error, Reason} = Err ->
		?ERROR_MSG("~p:~p ~p returned an error ~p~n", [Mod, Func, Args, Err]),
	        error_printing_sequence(Reason);
	    Result ->
		case is_string(Result) of
                    true  -> io:format("~s~n", [Result]);
                    false -> io:format("~p~n", [Result])
                end,
		Result
	end
    catch 
	_Class:Exception ->
	    ?ERROR_MSG("~p:~p ~p exited with ~p ~p~n", [Mod, Func, Args, Exception, erlang:get_stacktrace()]),
	    error_printing_sequence(Exception)
    end.

error_printing_sequence(Error) ->
    PrintableError = sanitize_error(Error),
    io:format("~p~n", [PrintableError]),
    io:format("~nSuggestions:~n"),
    print_user_error_msg(PrintableError),
    print_logfile_info(),
    io:format(" - Remember that if you are trying to install a package that 'faxien install <args...>'~n"),
    io:format("   is for releases, while 'faxien install_app <args...>' is for applications~n").
    
%%--------------------------------------------------------------------
%% @doc 
%%  Transform a mix of local paths and local app dirs into a list of 
%%  fully qualified ebin paths and add them to the code loader load path.
%% @spec add_pzs(LibDirs) -> list()
%% @end
%%--------------------------------------------------------------------
add_pzs(LibDirs) -> 
    lists:foreach(fun(LibDir) -> 
			  code:add_pathz(LibDir ++ "/ebin") 
		  end, to_app_dirs(LibDirs)).


%%--------------------------------------------------------------------
%% @doc 
%%  Transform a mix of local paths and local app dirs into a list of 
%%  fully qualified local app dirs.
%%  i.e ["../*"] -> ["/home/martin/otp/lib/resource_discovery", ... ]
%%  @spec to_app_dirs(LibDirs) -> list()
%% @end
%%--------------------------------------------------------------------
to_app_dirs(LibDirs) ->
    lists:foldl(fun(P, Acc) -> 
			case lists:reverse(P) of
			    [$*|_] -> [abs_path(X) || X <- filelib:wildcard(P)] ++ Acc;
			    _      -> [abs_path(P)|Acc]
			end
		end,
		[], LibDirs).
    

%%--------------------------------------------------------------------
%% @doc just a funny little helper to convert elements of a list that
%%      are atoms into strings, and leaves them alone if they are not.
%% @end
%%--------------------------------------------------------------------
if_atom_to_string(List) ->
    lists:map(fun(V) when is_atom(V) -> atom_to_list(V);
		 (V)                 -> V 
	      end, List).

%%-------------------------------------------------------------------
%% @doc
%% Return a list of versions. 
%%
%% @spec repo_list(Url) -> {ok, DirContents} | {error, Reason}
%%  where
%%   DirContents = list()
%% @end
%%-------------------------------------------------------------------
repo_list(Url) ->
    Opts = [{"Connection", "TE"},
	    {"TE", "trailers"},
	    {"Depth", "1"},
	    {"Content-Type", "application/xml"}],
    case ibrowse:send_req(Url, Opts, propfind, "") of
        {ok, "207", _, Body} -> 
	    ?ERROR_MSG("repo_list(~p) -> ~p~n", [Url, "success:207"]),
	    {ok, parse_out_package_versions(Body)};
	{ok, Code, _, _} = Res -> 
	    ?ERROR_MSG("repo_list(~p) -> ~p~n", [Url, Res]),
	    {error, {"No list found. http code: ", Code}};
        {error, _Reason} = Res -> 
	    ?ERROR_MSG("repo_list(~p) -> ~p~n", [Url, Res]),
	    Res;
        {'EXIT', Reason} = Exit -> 
	    ?ERROR_MSG("repo_list(~p) -> ~p~n", [Url, Exit]),
            {error, Reason}
    end.
    
parse_out_package_versions(Body) ->
    {Elem, _} = xmerl_scan:string(Body),
    [filename:basename(E) || E <- tl(lists:sort(xmerl_xs:value_of(xmerl_xs:select("//D:href", Elem))))].


							   


%%-------------------------------------------------------------------
%% @doc
%%  Finds files and directories that match the regexp supplied in the TargetPattern.
%%
%% @spec find(FromDir, TargetPattern) -> list()
%% @end
%%-------------------------------------------------------------------
find([], _) ->
    [];
find(FromDir, TargetPattern) ->
    case filelib:is_dir(FromDir) of
	false ->
	    case regexp:match(FromDir, TargetPattern) of 
		{match, _, _} -> [FromDir]; 
		_             -> []
	    end;
	true ->
	    FoundDir = case regexp:match(FromDir, TargetPattern) of 
		{match, _, _} -> [FromDir]; 
		_             -> []
	    end,
	    List = lists:foldl(fun(CheckFromDir, Acc) when CheckFromDir == FromDir -> 
				Acc;
			   (ChildFromDir, Acc) -> 
				case find(ChildFromDir, TargetPattern) of
				    []  -> Acc;
				    Res -> Res ++ Acc
				end
			end, [], filelib:wildcard(FromDir ++ "/*")),
	    FoundDir ++ List
    end.


%%-------------------------------------------------------------------
%% @doc
%%  Prints information about how to find the faxien logfiles.
%%
%% @spec print_logfile_info() -> void()
%% @end
%%-------------------------------------------------------------------
print_logfile_info() ->
    case gas:get_env(gas, err_log, undefined) of
	{ok, undefined} ->
	    ok;
	{ok, LogFile} ->
	    io:format(" - For error_logger information look at ~p~n", [LogFile])
    end,
    case gas:get_env(sasl, sasl_error_logger, undefined) of
	{ok, {file, SaslFile}} ->
	    io:format(" - For sasl log information look at ~p~n", [SaslFile]);
	_NoLog ->
	    ok
    end.
	    
%%----------------------------------------------------------------------------
%% @doc Applies a fun to all elements of a list until getting Return.
%% <pre>
%% This takes a fun, its expected return on 'success', and a list. The fun 
%% is applied to each element of the list untill the expected return is 
%% generated. If the expected return is generated from the application of 
%% the fun on an element of the list do_until halts and returns with Return. 
%% If not then the return value from the Fun being applied to the final 
%% element in the list is returned. If the list supplied is
%% empty then the return will be false. 
%% </pre>
%% @spec do_until(Fun, Return, list()) -> Return | Other | false
%% @end
%%----------------------------------------------------------------------------
do_until(_F, _, []) ->
    false;
do_until(F, _, [Last]) ->
    F(Last);
do_until(F, Return, [H|T]) ->
    case F(H) of
	Return -> Return;
	_      -> do_until(F, Return, T)
    end.
    
%% @spec do_until(Fun, list()) -> bool()
%% @equiv do_until(Fun, true, list())
do_until(F, List) ->
    do_until(F, true, List).

%%----------------------------------------------------------------------------
%% @doc Applies executable permissions to the file provided. 
%% @spec set_executable_perms(Filename) -> ok | {error, Reason}
%% @end
%%----------------------------------------------------------------------------
set_executable_perms(Filename) ->
    case erlang:system_info(system_architecture) of
	"win32" ->
	    ?INFO_MSG("fax_util:set_executable_perms(~p) SysArch was win32~n", [Filename]),
	    io:format("Win 32 is not yet supported.~n");
	SysArch ->
	    ?INFO_MSG("fax_util:set_executable_perms(~p) SysArch was ~p~n", [Filename, SysArch]),
	    case os:cmd("chmod a+x " ++ Filename) of
		[]    -> ok;
		Error -> {error, Error}
	    end
    end.

%%----------------------------------------------------------------------------
%% @doc Applies executable permissions recursively to all files in the directory.
%% @spec set_all_executable_perms(Dir) -> ok | {error, Reason}
%% @end
%%----------------------------------------------------------------------------
set_all_executable_perms(Dir) ->
    case erlang:system_info(system_architecture) of
	"win32" ->
	    ?INFO_MSG("fax_util:set_executable_perms(~p) SysArch was win32~n", [Dir]),
	    io:format("Win 32 is not yet supported.~n");
	SysArch ->
	    ?INFO_MSG("fax_util:set_executable_perms(~p) SysArch was ~p~n", [Dir, SysArch]),
	    case os:cmd("chmod -R a+x " ++ Dir) of
		[]    -> ok;
		Error -> {error, Error}
	    end
    end.

%%--------------------------------------------------------------------
%% @doc pull the erts vsn and the app specs from the supplied rel file. 
%% @spec consult_rel_file(RelFilePath) -> {ok, {ErtsVsn, AppSpecs}} | exit()
%% @end
%%--------------------------------------------------------------------
consult_rel_file(RelFilePath) ->
    {ok, [{release, _, {_, ErtsVsn}, AppSpecs}]} = file:consult(RelFilePath),
    {ok, {ErtsVsn, AppSpecs}}.
    
%%-------------------------------------------------------------------
%% @doc For each patch compatible erts vsn less than or equal to the TargetErtsVSn call the fun 
%% with a single erts vsn in the series. The fun takes a single arg - ErtsVsn::string(). 
%% @spec foreach_erts_vsn(TargetErtsVsn, Fun) -> ok | Error 
%% @end
%%-------------------------------------------------------------------
foreach_erts_vsn(TargetErtsVsn, Fun) ->
    [MajorErtsVsn, MinorErtsVsn, HighPatchErtsVsn] = string:tokens(TargetErtsVsn, "."),
    ErtsVsns = [lists:flatten([MajorErtsVsn, ".", MinorErtsVsn, ".", integer_to_list(E)]) || 
		   E <- lists:seq(0, list_to_integer(HighPatchErtsVsn))], 
    fax_util:do_until(fun(ErtsVsn) -> (catch Fun(ErtsVsn)) end, ok, lists:reverse(ErtsVsns)).

%%====================================================================
%% Internal functions
%%====================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Resolve a relative path turning it into an absolute path.
%% @end
%%--------------------------------------------------------------------
abs_path(RelativePath) ->
    {ok, InitialPath} = file:get_cwd(),
    file:set_cwd(RelativePath),
    {ok, AbsPath} = file:get_cwd(),
    file:set_cwd(InitialPath),
    AbsPath.


%%----------------------------------------------------------------------------
%% @private
%% @doc Flattens a complex term into a single string.
%% Note* References are not handled they will appear as reference
%% @spec flatten_term(term()) -> string()
%% @end
%%----------------------------------------------------------------------------
flatten_term(Terms) ->
    lists:concat(flattening(Terms)).
                                                                                   
flattening(Atom) when is_atom(Atom) ->
    [atom_to_list(Atom)];
flattening(Float) when is_float(Float) ->
    [float_to_list(Float)];
flattening(Integer) when is_integer(Integer) ->
    [integer_to_list(Integer)];
flattening(Binary) when binary(Binary) ->
    [binary_to_list(Binary)];
flattening(Pid) when pid(Pid) ->
    [pid_to_list(Pid)];
flattening(Ref) when is_reference(Ref) ->
    ["<<reference>>"];
flattening(Tuple) when tuple(Tuple) ->
    Terms = tuple_to_list(Tuple),
    Fun   = fun(Term, Acc) -> acc_check(Term, Acc) end,
    ["{"] ++ lists:foldl(Fun, [], Terms) ++ ["}"];
flattening(Terms) when list(Terms) ->
    case is_string(Terms) of
        true  -> 
            ["\"" ++ Terms ++ "\""];
        false ->
            Fun = fun(Term, Acc) -> acc_check(Term, Acc) end,
            ["["] ++ lists:foldl(Fun, [], Terms) ++ ["]"]
    end.
                                                                                   
acc_check(Term, [])  -> flattening(Term);
acc_check(Term, Acc) -> Acc ++ [", "] ++ flattening(Term).

%%----------------------------------------------------------------------------
%% @private
%% @doc Checks to see if a list is a string.
%% @spec is_string(List) -> bool()
%% @end
%%----------------------------------------------------------------------------
is_string([])                          -> true;
is_string(Term) when not is_list(Term) -> false;
is_string(Term)                        -> not lists:any(fun(Element) -> not_string_p1(Element) end, Term).
                                                                                   
not_string_p1(H) when integer(H), H >= $\s, H < 255 -> false;
not_string_p1($\n)                                  -> false;
not_string_p1($\r)                                  -> false;
not_string_p1($\t)                                  -> false;
not_string_p1($\v)                                  -> false;
not_string_p1($\b)                                  -> false;
not_string_p1($\f)                                  -> false;
not_string_p1($\e)                                  -> false;
not_string_p1(_)                                    -> true.


%%----------------------------------------------------------------------------
%% @private
%% @doc Print out a custom error message to the user based on a sanitized error term.
%% @end
%%----------------------------------------------------------------------------
print_user_error_msg({unable_to_pull_from_repos, Msg}) ->
    io:format(" - " ++ Msg ++ "~n   Please request that the package compiled for your local~n" ++
	      "   architecture be published to an accessible repository.~n");
print_user_error_msg({badmatch,{error,{error,req_timedout}}}) ->
    io:format(" - The request has timed out.  Specify a larger timeout value.~n");
print_user_error_msg({error,{package_not_found,Msg}}) ->
    io:format(" - " ++ Msg ++ "~n   Please request that a package of this type be compiled for your local~n" ++
	      "   architecture be published to an accessible repository.~n");
print_user_error_msg({error, {undef, _}}) ->
    io:format(" - Type 'faxien help' for information on the allowed commands.  Or visit www.erlware.org for more info.~n");
print_user_error_msg(_PrintableError) ->
    io:format(" - Make sure you have the correct permissions to run Faxien~n").

%%----------------------------------------------------------------------------
%% @private
%% @doc take a nasty error or exception term and clean it up so it's almost fit for human consumption.
%% @end
%%----------------------------------------------------------------------------
sanitize_error({badmatch, {error, {Msg, {'EXIT', {_Reason, _Trace}}}}}) when is_list(_Trace) ->
    {error, {package_not_found, Msg}};
sanitize_error({_Type, {error, _Reason} = Error}) ->
    Error;
sanitize_error({error, {{_Type, {error, _Reason} = Error}, _Trace}}) when is_list(_Trace) ->
    Error;
sanitize_error({Reason, _Trace}) when is_tuple(Reason), is_list(_Trace) ->
    Reason;
sanitize_error(undef) ->
    {error, {undef, "The function you are trying to execute does not exist."}};
sanitize_error(Error) when is_atom(Error) ->
    {error, {unknown, "unhandled error: please see the Faxien log files for more details on this error."}};
sanitize_error(Error) ->
    Error.

%%----------------------------------------------------------------------------
%% @private
%% @doc Windows sometimes uses spaces in its directory names, if a \ is found at the end of a string connect it with the next string
%% @end
%%----------------------------------------------------------------------------
no_space(Args) ->
    no_space(Args, []).

no_space([], Acc) ->
    lists:reverse(Acc);
no_space([E], Acc) ->
    lists:reverse([E|Acc]);
no_space([A1, A2|T], Acc) ->
    A1S = atom_to_list(A1),
    A2S = atom_to_list(A2),
    case hd(lists:reverse(A1S)) of
	$\\ -> no_space(T,[list_to_atom(A1S ++ " " ++ A2S)|Acc]);
	_   -> no_space([A2|T], [A1|Acc])
    end.
	    

convert_string_to_terms(ArgString) ->
    ?INFO_MSG("arg string ~p~n", [ArgString]),
    case regexp:match(ArgString, "^[0-9\.-]+$") of
	{match, _, _} -> 
	    special_case("decimal conversion to string", ArgString);
	_  ->
	    ScanableArg       = ArgString ++ ". ", 
	    {ok, Toks, _Line} = erl_scan:string(ScanableArg, 1),
	    case catch erl_parse:parse_term(Toks) of
		{ok, Term} -> 
		    Term;
		Error ->
		    special_case(Error, ArgString)
	    end
    end.

special_case(Msg, ArgString) ->
    ?INFO_MSG("Special case discovered for ~p converting input arg to string and reprocessing~n", [Msg]),
    convert_string_to_terms("\"" ++ ArgString ++ "\"").
    


%%%===================================================================
%%% Testing Functions
%%%===================================================================
is_string_test() ->
    ?assertMatch(false, is_string([hello])),
    ?assertMatch(false, is_string({})),
    ?assertMatch(true, is_string("hell]o")),
    ?assertMatch(true, is_string("hello")).

